package com.demo.config;

import static com.demo.util.DateContants.FULL_TIME_FORMATTER;
import static com.demo.util.DateContants.TIME_FORMATTER;
import static com.demo.util.DateContants.YEAR_MONTH_DAY_FORMATTER;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.net.ssl.SSLContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.demo.log.LogFilter;
import com.demo.util.JacksonJsonUtils;
import com.demo.web.AuthenticationInterceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

@Configuration
public class CommonWebConfig implements WebMvcConfigurer {

    @Autowired
    private AuthenticationInterceptor authenticationInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       
        // 权限认证
        registry.addInterceptor(authenticationInterceptor).order(Ordered.HIGHEST_PRECEDENCE + 1).addPathPatterns("/**")
                .excludePathPatterns("/sso/login","/sso/refresh"
                    ,"/v2/**"
                    ,"/v3/**"
                    ,"/doc.html"
                    ,"/favicon.ico"
                    ,"/**swagger-ui.html/**"
                    ,"/**swagger-resources/**"
                    ,"/**error**"
                    ,"/**webjars/**");
    }
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        registry.addResourceHandler("/favicon.ico").addResourceLocations("classpath:/static/favicon.ico");
    }

    /**
     * 此类创建ObjectMapper 解决Date,LocalDateTime,Long的解析
     * 
     * @return
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {

        return (builder -> {
            JacksonJsonUtils.configureBuilder(builder);
            builder.serializerByType(Long.class, ToStringSerializer.instance)
                .serializerByType(BigInteger.class, ToStringSerializer.instance)
                .serializerByType(BigDecimal.class, ToStringSerializer.instance);
        });
    }

    @Bean
    @Primary
    @ConditionalOnClass(ObjectMapper.class)
    @ConditionalOnMissingBean({ObjectMapper.class})
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.setLocale(Locale.ENGLISH);
        return objectMapper;
    }

    @Bean
    @Primary
    public RestTemplate restTemplate(ObjectMapper objectMapper) throws Exception {

        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {

            @Override
            public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                return true;
            }

        }).build();

        SSLConnectionSocketFactory csf =
            new SSLConnectionSocketFactory(sslContext, new String[] {"TLSv1"}, null, NoopHostnameVerifier.INSTANCE);
        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);
        requestFactory.setReadTimeout(600000);// 超时时间增加为10分钟
        requestFactory.setConnectTimeout(60000);// 连接时间增加为1分钟
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        // 使用 utf-8 编码集的 conver 替换默认的 conver（默认的 string conver 的编码集为"ISO-8859-1"）
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator();
        while (iterator.hasNext()) {
            HttpMessageConverter<?> converter = iterator.next();
            if (converter instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter)converter).setDefaultCharset(StandardCharsets.UTF_8);
            } else if (converter instanceof MappingJackson2HttpMessageConverter) {
                ((MappingJackson2HttpMessageConverter)converter).setObjectMapper(objectMapper);
            }
        }
        return restTemplate;
    }

    /**
     * <pre>
     *  attention:简单跨域就是GET，HEAD和POST请求
     *  但是POST请求的"Content-Type"只能是application/x-www-form-urlencoded, multipart/form-data 或 text/plain 
     *  反之，就是非简单跨域，此跨域有一个预检机制，说直白点，就是会发两次请求，一次OPTIONS请求，一次真正的请求
     *  只有在OPTIONS中才会设置response的请求头信息
     * </pre>
     */
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        // 允许cookies跨域
        config.setAllowCredentials(true);
        // #允许向该服务器提交请求的URI，*表示全部允许，在SpringMVC中，如果设成*，会自动转成当前请求头中的Origin
        config.addAllowedOriginPattern("*");
        // config.addAllowedOrigin("*");
        // #允许访问的头信息,*表示全部
        config.addAllowedHeader("*");
        // 预检请求的缓存时间（秒），即在这个时间段里，对于相同的跨域请求不会再预检了
        config.setMaxAge(18000L);
        // 允许提交请求的方法，*表示全部允许
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        // 允许Get的请求类型
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        config.addExposedHeader("Content-Disposition");// 允许Ajax下载文件获取此请求头信息
        config.setMaxAge(Duration.ofHours(3));// 3个小时不用发预检
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    @Bean
    public FilterRegistrationBean<LogFilter> logFilterRegister() {
        FilterRegistrationBean<LogFilter> registration = new FilterRegistrationBean<>();
        // 注入过滤器
        registration.setFilter(new LogFilter());
        // 拦截规则
        registration.addUrlPatterns("/*");
        // 过滤器名称
        registration.setName("LogFilter");
        // 过滤器顺序
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }

    /******** 普通请求参数中的时间转换 ******/
    @Bean
    public StringToLocalDateTimeConverter localDateTimeConverter() {
        return new StringToLocalDateTimeConverter();
    }

    @Bean
    public StringToLocalDateConverter localDateConverter() {
        return new StringToLocalDateConverter();
    }

    @Bean
    public StringToLocalTimeConverter localTimeConverter() {
        return new StringToLocalTimeConverter();
    }

    private static class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {

        @Override
        public LocalDateTime convert(String source) {
            try {
                if (!StringUtils.isBlank(source)) {
                    if (source.length() == 10) {
                        return LocalDateTime.of(LocalDate.parse(source, YEAR_MONTH_DAY_FORMATTER), LocalTime.MIN);
                    } else {
                        return LocalDateTime.parse(source, FULL_TIME_FORMATTER);
                    }
                }
            } catch (Exception e) {

            }
            return null;
        }

    }

    private static class StringToLocalDateConverter implements Converter<String, LocalDate> {

        @Override
        public LocalDate convert(String source) {
            if (!StringUtils.isBlank(source)) {
                try {
                    return LocalDate.parse(source, YEAR_MONTH_DAY_FORMATTER);
                } catch (Exception e) {
                }
            }
            return null;
        }

    }

    private static class StringToLocalTimeConverter implements Converter<String, LocalTime> {

        @Override
        public LocalTime convert(String source) {
            if (!StringUtils.isBlank(source)) {
                try {
                    return LocalTime.parse(source, TIME_FORMATTER);
                } catch (Exception e) {
                }
            }
            return null;
        }

    }

}
